In [1]:
import pandas as pd

from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
import plotly
import plotly.graph_objs as go
from datetime import date, timedelta
import plotly.express as px
import numpy as np
In [2]:
df = pd.read_csv('../data/report_task_2.csv')
In [3]:
df['Turn_off'] = False
df['profit'] = df['bids'] * df['bidFloor']
In [4]:
df.head()
Out[4]:
adType bidFloor country agencyId requests bids appId Turn_off profit
0 banner 0.0 BR 100 46 0 c7a7998861eb8dd38207b42beeb14eb53e34d47f4af66a... False 0.0
1 banner 0.0 BR 103 4 0 5ad8271cb4a89b09cd947c10f83cc93679169790370854... False 0.0
2 banner 0.0 BR 103 29 0 5de2c26b35b45d1aa1f4140c89e1d36a3eb111ca3da03a... False 0.0
3 banner 0.0 BR 103 2 0 6fcb8648f07e7e2c4af6448252410bc212ebd1feccf4dc... False 0.0
4 banner 0.0 BR 103 3 0 4ed963eceddaaf4cc17863a954cc9a102e4dad45fc42ab... False 0.0

Также нам нужно определить суммарный доход, и границу потерь

In [5]:
total_profit = int(df['profit'].sum())
decrease_allowed = int(total_profit * 0.05)
print(f'Total profit: {total_profit}, decrease in profit is allowed: {decrease_allowed}')
Total profit: 1645755, decrease in profit is allowed: 82287
In [6]:
temp = df.copy()
In [7]:
temp = temp.groupby(['country', 'appId', 'agencyId', 'adType', 'bidFloor']).sum().reset_index()
temp['profit_diff_requests'] = temp['profit'] / temp['requests']

Сразу отправим на удаление те случае, когда bidFloor=0 и мы ничего не зарабатываем внезависимости от того, была ставка или нет.
Также исключим те случае, когда мы ничего не заработали, так как отсуствовали ставки:

In [8]:
temp.loc[(temp['bidFloor']==0), 'Turn_off'] = True
requests_save = df[(df['bidFloor']==0)]['requests'].sum()
print(f'Requests save: {requests_save}')
Requests save: 564871
In [9]:
temp.loc[temp['profit']==0, 'Turn_off'] = True
requests_save += temp[temp['profit']==0]['requests'].sum()
print(f'Requests save: {requests_save}')
Requests save: 239239696

Посмотрим на расспределение соотношения профита к риквестам. Ограничем его справа 0.1, для лучшей наглядности.
Как мы видим, у нас очень много риквестов сконцентрированно на низко профитном трафике.

In [10]:
fig = px.histogram(temp[(temp['profit_diff_requests']<0.1)
                       &(temp['Turn_off']==False)][['profit_diff_requests', 'requests']], 
                   x='profit_diff_requests', 
                   y='requests', histfunc="sum", nbins=10)
fig.show()

Посмотрим, сколько денег мы потеряем, если его отключим:

In [12]:
t = temp[(temp['profit_diff_requests']<0.01)&(temp['Turn_off']==False)]
decrease_in_profit = int(t['profit'].sum())
r_s = t['requests'].sum()
print(f'Requests save: {r_s}, decrease in profit: {decrease_in_profit}')
Requests save: 47229459, decrease in profit: 81612

Согласно нашим ограничением, мы можем потерять 82287, при текущих ограничениях теряем 81612. Исключим этот трафик.

In [13]:
temp.loc[(temp['profit_diff_requests']<0.01)&(temp['Turn_off']==False), 'Turn_off'] = True
In [14]:
t = temp[temp['Turn_off']==True]
decrease_in_profit = int(t['profit'].sum())
r_s = t['requests'].sum()
print(f'Requests save: {r_s}, decrease in profit: {decrease_in_profit}')
Requests save: 285904284, decrease in profit: 81612

В итоге, мы можем уменьшить объем риквествов на 285М, при этом потеряв всего 81612, что составляет ~5%

In [19]:
temp[temp['Turn_off']==True][['country', 'appId', 'agencyId', 
                              'adType', 'bidFloor','requests']].to_csv('../data/saved_requests.csv', index=False)

Вывод:
Аналитический подход имеет место быть, но считаю его не самым оптимальным, там как на самом деле на ставку или ее отсуствие от ДСП влияет очень много параметров: сезонность, информация с риквества (девайс, вид рекламы, гео, пользователь и тд), история бидов этой ДСП и тд.
Это все приводит нас к использованию машинного обучения для решения подобных задач.
Самый простой вариант:
Для каждого риквества мы считаем вероятность бида и затем используем трешхолд по которому исключаем трафик. Для управления трехолдом можно использовать PID контроллер.

In [ ]: